<?php
/**
 * This file is part of workerman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link http://www.workerman.net/
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 */

/**
 * 用于检测业务代码死循环或者长时间阻塞等问题
 * 如果发现业务卡死，可以将下面declare打开（去掉//注释），并执行php start.php reload
 * 然后观察一段时间workerman.log看是否有process_timeout异常
 */
//declare(ticks=1);

use \GatewayWorker\Lib\Gateway;

/**
 * 主逻辑
 * 主要是处理 onConnect onMessage onClose 三个方法
 * onConnect 和 onClose 如果不需要可以不用实现并删除
 */
class Events
{
    /**
     * 新建一个类的静态成员，用来保存数据库实例
     */
    public static $db = null;
    public static $config = null;
    /**
     * 进程启动后初始化数据库连接
     */
    public static function onWorkerStart($worker)
    {
        self::$config = array_merge(
            require (__DIR__ . '/config/main.php'),
            require (__DIR__ . '/config/main_local.php')
        );
        self::$db = new \Workerman\MySQL\Connection($config['db']['host'], $config['db']['port'], $config['db']['user'], $config['db']['password'], $config['db']['db_name']);
    }

    public static function onWebSocketConnect($client_id, $data)
    {
        $map['token'] = $data['get']['token'];
        $detail_token = self::$db->select('*')->from('db_book_token')->where('token= :token')->bindValues($map)->row();
        if ($detail_token && $detail_token['create_at'] < time() + 3600) {
            $_SESSION['bookid'] = $detail_token['bookid'];
            Gateway::sendToClient($client_id, 'ready');
        } else {
            //过期
            Gateway::sendToClient($client_id, json_encode(['type' => 'abort', 'data' => '权限不足或者令牌已过期，请刷新页面重新连接']));
        }
    }

    /**
     * 当客户端连接时触发
     * 如果业务不需此回调可以删除onConnect
     *
     * @param int $client_id 连接id
     */
    public static function onConnect($client_id)
    {
        echo 'onConnect' . PHP_EOL;
        // 向当前client_id发送数据
        // Gateway::sendToClient($client_id, 'ready');
        // 向所有人发送
        // Gateway::sendToAll("$client_id login\r\n");
    }

    /**
     * 当客户端发来消息时触发
     * @param int $client_id 连接id
     * @param mixed $message 具体消息
     */
    public static function onMessage($client_id, $message)
    {
        $bookid = (int) $_SESSION['bookid'];
        echo 'onMessage:' . $client_id . PHP_EOL;
        //心跳包
        if ($message == 'beat') {
            GateWay::sendToCurrentClient('beat');
            return false;
        }

        $message_arr = json_decode($message, true);
        var_dump($message_arr);
        $return_data         = [];
        $data                = null;
        $return_data['type'] = $message_arr['type'];
        $return_data['id']   = $message_arr['id'];

        $detail_book = self::$db->select('*')->from('db_book')->where('id= :bookid')->bindValues(['bookid' => $bookid])->row();

        switch ($message_arr['type']) {
            case 'state.read':
                //状态读取
                $data['current'] = isset($detail_book['current']) ? $detail_book['current'] : null;
                $data['changes'] = empty($detail_book['changes']) ? [] : json_decode($detail_book['changes']);
                break;
            case 'file.read':
                //读取内容
                $map['filename'] = $message_arr['data']['filename'];
                $map['bookid']   = $bookid;
                $result          = self::$db->select('content')->from('db_book_chapter')->where('filename= :filename AND bookid= :bookid')->bindValues($map)->row();
                $data            = $result['content'];
                break;
            case 'file.write':
                //获取book的changes
                $changes = $detail_book['changes'] ? json_decode($detail_book['changes'], true) : [];
                //内容更改
                $data = $changes;
                $map['filename']  = $message_arr['data']['filename'];
                $map['bookid']    = $bookid;
                $detail_chapter   = self::$db->select('content,sha,id')->from('db_book_chapter')->where('filename= :filename AND bookid= :bookid')->bindValues($map)->row();
                $info['bookid']   = $bookid;
                $info['filename'] = $message_arr['data']['filename'];
                $info['content']  = $message_arr['data']['content'];
                if($info['content'] == '' && isset($detail_chapter) && empty($detail_chapter['sha'])){
                    //删除
                    self::$db->delete('db_book_chapter')->where('id='.$detail_chapter['id'])->query();
                }else{
                    if (empty($detail_chapter) || empty($detail_chapter['sha'])) {
                        //新增动作
                        $info['type'] = 'create';
                        $data[$message_arr['data']['filename']]['type'] = 'create';
                    } else {
                        //更新动作
                        if(sha1(base64_decode($info['content'])) != $detail_chapter['sha']){
                            $data[$message_arr['data']['filename']]['type'] = 'update';
                            $info['type'] = 'update';
                        }else{
                            $info['type'] = '';
                            unset($data[$message_arr['data']['filename']]);
                        }
                    }
                    if (empty($detail_chapter)) {
                        self::$db->insert('db_book_chapter')->cols($info)->query();
                    } else {
                        unset($info['bookid'], $info['filename']);
                        self::$db->update('db_book_chapter')->cols($info)->where("filename= :filename AND bookid= :bookid")->bindValues($map)->query();
                    }
                }

                //更新book的changes
                if($data != $changes){
                    //更新changes
                    $new_changes = empty($data) ? '' : json_encode($data);
                    self::$db->update('db_book')->cols(['changes' => $new_changes])->where("id=".$bookid)->query();
                }
                
                //获取
                break;
            case 'file.rename':
                //更改文件名
                $map['filename'] = $message_arr['data']['old_path'];
                $map['bookid']   = $bookid;
                self::$db->update('db_book_chapter')->cols(['filename' => $message_arr['data']['path']])->where('filename= :filename AND bookid= :bookid')->bindValues($map)->query();
                # code...
                break;
            case 'file.remove':
                //获取book的changes
                $changes = $detail_book['changes'] ? json_decode($detail_book['changes'], true) : [];
                //内容更改
                $data = $changes;
                $map['filename']  = $message_arr['data'];
                $map['bookid']    = $bookid;
                $detail_chapter   = self::$db->select('content,sha,id')->from('db_book_chapter')->where('filename= :filename AND bookid= :bookid')->bindValues($map)->row();

                //如果在版本库里
                if($detail_chapter && !empty($detail_chapter['sha'])){
                    $data[$map['filename']]['type'] = 'remove';
                    $info['type'] = 'remove';
                    $info['content'] = '';
                    self::$db->update('db_book_chapter')->cols($info)->where("id=".$detail_chapter['id'])->query();
                }else{
                    //直接删除
                    self::$db->delete('db_book_chapter')->where("id=".$detail_chapter['id'])->query();
                    if(isset($data[$map['filename']])){
                        unset($data[$map['filename']]);
                    }
                }

                //更新book的changes
                if($data != $changes){
                    //更新changes
                    $new_changes = empty($data) ? '' : json_encode($data);
                    self::$db->update('db_book')->cols(['changes' => $new_changes])->where("id=".$bookid)->query();
                }
                break;
            case 'state.sync':
                //同步到git
                $repos_name = $detail_book['repos_name'];
                //获取所有修改的文件
                $map['bookid'] = $detail_book['id'];
                $map['status'] = 1;

                $chapter_list   = self::$db->select('*')->from('db_book_chapter')->where('status= :status AND bookid= :bookid')->bindValues($map)->query();
                if(!empty($chapter_list)){
                    //获取当前分支引用
                    $current_commit_sha = $detail_book['ref'];
                    //分别创建blob
                    $trees = $list_remove_file = [];
                    foreach ($chapter_list as $content) {
                        //如果type=remove，则删除
                        if($content['type'] == 'remove'){
                            $list_remove_file[] = $content['id'];
                            continue;
                        }
                        $tree = [];
                        $tree['path'] = $content['filename'];
                        $tree['mode'] = '100644';
                        $tree['blob'] = 'blob';
                        //获取文件sha，如果文件未修改， 则直接取原文件的blob_sha
                        //文件未更改，且有
                        if(empty($content['type']) && !empty($content['blob_sha'])){
                            $tree['sha'] = $content['blob_sha'];
                        }else{
                            $tree['content'] = base64_decode($content['content']);
                        }
                        //保留现有的sha
                        $list_file[$content['filename']] = $content;
                        $trees[] = $tree;
                    }

                    try{
                        if(empty($current_commit_sha)){
                            $current_commit_sha = self::gitCurrentCommit($repos_name);
                        }
                        var_dump($current_commit_sha);

                        //创建tree
                        $tree_git = self::gitCreateTree($repos_name, $trees);
                        var_dump($tree_git['sha']);
                        //创建commit
                        $commit_response = self::gitCreateCommit($repos_name, $tree_git['sha'], $current_commit_sha);

                        var_dump($commit_response['sha']);
                        //更新分支引用
                        $ref = self::gitUpdateReference($repos_name, $commit_response['sha']);
                        var_dump($commit_response['sha']);

                        //更新book当前分支
                        $book_update_info['ref'] = $ref;
                        $book_update_info['changes'] = '';
                        $book_update_info['tree'] = $commit_response['tree']['sha'];
                        // var_dump($book_update_info);['ref' => $ref]
                        self::$db->update('db_book')->cols($book_update_info)->where('id='.$detail_book['id'])->query();

                        //循环保留tree中的sha数据
                        foreach ($tree_git['tree'] as $value) {
                            if(!empty($list_file[$value['path']]['type'])){
                                $chapter_update_info['blob_sha'] = $value['sha'];
                                $chapter_update_info['sha'] = sha1(base64_decode($list_file[$value['path']]['content']));
                                $chapter_update_info['type'] = '';
                                self::$db->update('db_book_chapter')->cols($chapter_update_info)->where('id='.$list_file[$value['path']]['id'])->query();
                            }
                        }
                        //删除remove的章节
                        if(!empty($list_remove_file)){
                            foreach ($list_remove_file as $value) {
                                self::$db->delete('db_book_chapter')->where('id= :id')->bindValues(['id' => $value])->query();
                            }
                        }
                    }catch(Exception $e){
                        echo 'Message: ' .$e->getMessage();
                    }
                    
                }
                
                $data['changes'] = [];
                
                break;
            case 'history.read':
                $data = [];
                $response = self::gitListCommits($detail_book['repos_name']);
                foreach ($response as $value) {
                    $info = $value['commit'];
                    $info['sha'] = $value['sha'];
                    $data[] = $info;
                }
                // $data = $response;
                break;
            case 'history.diff':
                $sha = $message_arr['data'];
                $detail_commit = self::gitGetCommit($detail_book['repos_name'], $sha);
                $data['commit'] = $detail_commit['commit'];
                $diff = $detail_commit['files'];
                foreach ($diff as $key => $value) {
                    $diff[$key]['patch'] = str_replace('\ No newline at end of file', "", $value['patch']);
                }
                $data['diff'] = $diff;
                break;
            case 'state.setCurrent':
                //更改当前激活目录
                $map['bookid'] = $bookid;
                self::$db->update('db_book')->cols(['current' => $message_arr['data']])->where("id= :bookid")->bindValues($map)->query();
                break;
            default:
                # code...
                break;
        }
        $return_data['data'] = $data;
        GateWay::sendToCurrentClient(json_encode($return_data));
        //如果没有type，则代表第一次连接，返回ready
        // GateWay::sendToCurrentClient(json_encode(array('data'=>'ready', 'isTrusted' => 'true')));
        // 向所有人发送
        // Gateway::sendToAll("$client_id said $message\r\n");
    }

    /**
     * 当用户断开连接时触发
     * @param int $client_id 连接id
     */
    public static function onClose($client_id)
    {
        echo 'onClose' . PHP_EOL;
        // 向所有人发送
        GateWay::sendToAll("$client_id logout\r\n");
    }

    public static function getGitApi($url, $param = [], $type="get",$data=array(),$timeout = 10){

        $access_token = self::$config['git']['token'];
        $url = 'https://api.github.com'.$url;
        $header = [];
        if($type != 'get'){
            //GIT_ACCESS_TOKEN需自行配置
            $header[] = 'Authorization: token '.$access_token;
        }
        $content = self::getCurlData($url, $param, $type, $data, 10, $header);
        return $content;
    }


    public static function gitCreateTree($repos_name, $trees){
        ////repos/:owner/:repo/git/trees
        $url = sprintf('/repos/%s/%s/git/trees', self::$config['git']['owner'], $repos_name);
        $data['tree'] = $trees;

        $response_data = self::getGitApi($url, [], 'post', $data);
        $response = json_decode($response_data, true);
        if($response['sha']){
            return $response;
        }else{
            return false;
        }
    }

    public static function gitGetCommit($repos_name, $commit_sha){
        //GET /repos/:owner/:repo/commits/:commit_sha
        $url = sprintf('/repos/%s/%s/commits/%s', self::$config['git']['owner'], $repos_name, $commit_sha);

        $response_data = self::getGitApi($url, [], 'get');
        $response = json_decode($response_data, true);
        if(!empty($response)){
            return $response;
        }else{
            return false;
        }
    }


    public static function gitListCommits($repos_name){
        //GET /repos/:owner/:repo/commits
        $url = sprintf('/repos/%s/%s/commits', self::$config['git']['owner'], $repos_name);

        $response_data = self::getGitApi($url, [], 'get');
        $response = json_decode($response_data, true);
        // var_dump($response);
        if(!empty($response)){
            return $response;
        }else{
            return false;
        }
    }

    public static function gitCurrentCommit($repos_name){
        //GET repos/:owner/:repo/commits
        $url = sprintf('/repos/%s/%s/commits', self::$config['git']['owner'], $repos_name);

        $response_data = self::getGitApi($url, [], 'get');
        $response = json_decode($response_data, true);
        if(!empty($response)){
            return $response[0]['sha'];
        }else{
            return false;
        }
    }

    public static function gitCreateCommit($repos_name, $tree_sha, $parents_commit_sha){
        ////repos/:owner/:repo/git/commits
        $url = sprintf('/repos/%s/%s/git/commits', self::$config['git']['owner'], $repos_name);
        $data['tree'] = $tree_sha;
        $data['message'] = '更新章节';
        $data['parents'][] = $parents_commit_sha;

        $response_data = self::getGitApi($url, [], 'post', $data);
        $response = json_decode($response_data, true);
        if(!empty($response)){
            return $response;
        }else{
            return false;
        }
    }

    public static function gitUpdateReference($repos_name, $sha){
        //PATCH /repos/:owner/:repo/git/refs/:ref
        $url = sprintf('/repos/%s/%s/git/refs/%s', self::$config['git']['owner'], $repos_name, 'heads/master');
        $data['sha'] = $sha;
        // $data['force'] = true;

        $response_data = self::getGitApi($url, [], 'patch', $data);
        $response = json_decode($response_data, true);
        if($response['object']){
            return $response['object']['sha'];
        }else{
            return false;
        }
    }

    public static function gitCreateBlob($repos_name, $info){
        ///repos/:owner/:repo/git/blobs
        $url = sprintf('/repos/%s/%s/git/blobs', self::$config['git']['owner'], $repos_name);
        $data['content'] = $info['content'];
        $data['encoding'] = 'base64';

        $response_data = self::getGitApi($url, [], 'post', $data);
        $response = json_decode($response_data, true);
        if($response['sha']){
            return $response['sha'];
        }else{
            return false;
        }
    }

    public static function getCurlData($url, $param = [], $type = "get", $data = array(), $timeout = 10, $header = [])
    {
        //对空格进行转义
        $url = str_replace(' ', '+', $url);

        if (!empty($param) && is_array($param)) {
            $arr = [];
            foreach ($param as $k => $v) {
                if (strstr($url, "?")) {
                    $url .= "&" . $k . "=" . urlencode($v);
                } else {
                    $url .= "?" . $k . "=" . urlencode($v);
                }
            }
        }
        $data = json_encode($data);
        // $url = urldecode($url);
        // echo $url ;exit;
        $curl = curl_init();
        //设置选项，包括URL
        curl_setopt($curl, CURLOPT_URL, "$url");

        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); //这个是重点。
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); //定义超时3秒钟
        $header[] = "Content-Type:application/json;charset=utf-8";
        $header[] = "Accept: application/json";
        $header[] = "User-Agent: Awesome-Octocat-App";
        switch (strtoupper($type)) {
            case "GET":
                curl_setopt($curl, CURLOPT_HTTPGET, true); //TRUE 时会设置 HTTP 的 method 为 GET，由于默认是 GET，所以只有 method 被修改时才需要这个选项。
                break;
            case "POST":
                // POST数据
                curl_setopt($curl, CURLOPT_POST, 1);
                // 把post的变量加上
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
                break;
            case "PATCH":
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PATCH");
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
                break;
            case "PUT":
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT");
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
                break;
            case "DELETE":
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
                curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
                break;
        }

        if (!empty($header)) {
            curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        }
        //执行并获取url地址的内容
        $output = curl_exec($curl);
        //echo $output ;
        //释放curl句柄
        curl_close($curl);

        return $output;
    }
}
